/* Copyright 2009 Requirement Management System
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mags.dao.provider;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Iterator;

import org.mags.dao.common.Aggregate;
import org.mags.dao.common.DAO;
import org.mags.dao.common.DBOperation;
import org.mags.dao.common.DatabaseContext;
import org.mags.dao.common.DataProvider;
import org.mags.dao.common.EntityDefinition;
import org.mags.dao.common.EntityProperties;
import org.mags.dao.common.Limits;
import org.mags.dao.common.MDAO;
import org.mags.dao.common.MetadataProvider;
import org.mags.dao.common.SOrder;
import org.mags.dao.common.RestrictionAttribute;
import org.mags.dao.common.RestrictionBuilder;
import org.mags.dao.common.RestrictionBuilderFactory;
import org.mags.dao.common.Transaction;
import org.mags.dao.common.Attribute;

/**
 * @author Mario Gait�n
 */

public abstract class AbstractDataProvider<T> 
	implements Serializable,DataProvider<T>, RestrictionBuilderFactory
	, MetadataProvider {
	private static final long serialVersionUID = 1L;
	private Class<T> persistentClass=null;
	private DatabaseContext dataContext;
	private EntityDefinition defaultEntityDefinition;

	protected void setDefaultEntityDefinition(EntityDefinition defaultEntityDefinition) {
		this.defaultEntityDefinition = defaultEntityDefinition;
	}
	protected EntityDefinition getDefaultEntityDefinition() {
		if(defaultEntityDefinition==null)
			defaultEntityDefinition = new EntityDefinition(getPersistentClass());
		return defaultEntityDefinition;
	}
	@Override
	public DatabaseContext getDataContext() {
		return dataContext;
	}
	@Override
	public void setDataContext(DatabaseContext dataContext) {
		this.dataContext = dataContext;
	}
	@Override
	public Object execute(DBOperation operation) {
		return getDao().execute(operation);
	}
	
	@Override
	public Object executeInTransaction(Transaction transaction) {
		return getDao().executeInTransaction(transaction);
	}
	
	protected RestrictionBuilderFactory getRestrictionBuilderCreator() {
		return getDataContext().getRestrictionBuilderFactory();
	}
	protected MDAO getMdao() {
		return getDataContext().getMdao();
	}
	protected DAO getDao() {
		return getDataContext().getDao();
	}
	@Override
	public RestrictionBuilder createRestrictionBuilder() {
		return getRestrictionBuilderCreator().createRestrictionBuilder();
	}
	@Override
	public Class<T> getPersistentClass() {
		if(persistentClass==null) {
			ParameterizedType parameterizedType=null;
			Object clazz = getClass();
			while(parameterizedType==null) {
				if(clazz instanceof ParameterizedType) 
					parameterizedType = (ParameterizedType)clazz;
				else if(clazz!=null)
					clazz = ((Class)clazz).getGenericSuperclass();
				else
					throw new RuntimeException("provider must be parameterized with pojo class");
			}
			persistentClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
		}
		return persistentClass;
	}
	@Override
	public T newInstance() {
		try {
			return getPersistentClass().newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	protected Object getAggregate(EntityProperties entityProperties, Aggregate agg, String propertyName, RestrictionBuilder restriction) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return getDao().getAggregate(entityDefinition, agg, propertyName, restriction);
	}
	@Override
	public void createOrUpdate(T record) {
		getDao().createOrUpdate(record);
	}
	@Override
	public Serializable create(T record) {
		return getDao().create(record);
	}
	@Override
	public Number update(Attribute[] attributes, RestrictionBuilder restriction) {
		return getDao().update(getDefaultEntityDefinition(), attributes, restriction);
	}
	@Override
	public void update(T record) {
		getDao().update(record);
	}	
	@Override
	public void delete(T record) {	
		getDao().delete(record);
	}		
	@SuppressWarnings("unchecked")
	@Override
	public T getByIdForUpdate(Serializable id) {
		return (T)getDao().getByIdForUpdate(getDefaultEntityDefinition(), id);
	}
	@SuppressWarnings("unchecked")
	public T getById(Serializable id) {		
		return (T)getDao().getById(getDefaultEntityDefinition(), id);
	}
	@SuppressWarnings("unchecked")
	protected Collection<T> getAll(EntityProperties entityProperties, SOrder[] orderBy) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		Collection<?> collection = getDao().getAll(entityDefinition, orderBy);
		return (Collection<T>)collection;
	}
	protected Number count(EntityProperties entityProperties, String projection, RestrictionBuilder restriction) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return getDao().count(entityDefinition, projection, restriction);
	}
	protected Number count(EntityProperties entityProperties, RestrictionBuilder restriction) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return getDao().count(entityDefinition, restriction);
	}
	@Override
	public boolean existsWithId(Serializable id) {
		return getDao().existsWithId(getDefaultEntityDefinition(), id);
	}
	protected boolean existsWithRestriction(EntityProperties entityProperties, RestrictionBuilder restriction) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return getDao().existsWithRestriction(entityDefinition, restriction);
	}		
	protected Number executeUpdate( String query, Attribute[] attributes) {
		return getDao().executeUpdate(query, attributes);
	}	
	protected Object getObject( String query,  Attribute[] attributes,  Limits limits) {
		return getDao().executeQuery(query, attributes, limits);
	}
	protected Object createSubquery(EntityProperties entityProperties, String[] projections, RestrictionBuilder restriction) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return getDao().createSubquery(entityDefinition, projections, restriction);
	}
	protected Collection getByRestriction(EntityProperties entityProperties, String[] projections, RestrictionBuilder restriction, SOrder[] orderBy, Limits limits) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		return (Collection)getDao().getByRestriction(entityDefinition, projections, restriction, orderBy, limits);
	}
	@SuppressWarnings("unchecked")
	protected Collection<T> getByRestriction(EntityProperties entityProperties, RestrictionBuilder restriction, SOrder[] orderBy, Limits limits) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		Collection<?> collection = getDao().getByRestriction(entityDefinition, restriction, orderBy, limits);
		return (Collection<T>)collection;
	}
	protected Collection executeQuery( String query,  Attribute[] attributes,  Limits limits) {
		return getDao().executeQuery(query, attributes, limits);
	}
	@SuppressWarnings("unchecked")
	protected Collection<T> getByRestrictionAsMaps(EntityProperties entityProperties, RestrictionBuilder restriction, SOrder[] orderBy, Limits limits) {
		EntityDefinition entityDefinition = new EntityDefinition(getPersistentClass(), entityProperties);
		Collection<?> collection = getDao().getByRestrictionAsMaps(entityDefinition, restriction, orderBy, limits);
		return (Collection<T>)collection;
	}
	private T getFirst(Collection<T> list) {
		Iterator<T> iterator = list.iterator();
		if(iterator.hasNext())
			return iterator.next();
		else
			return null;
	}
	protected T getOneByRestriction(RestrictionBuilder restriction) {
		return getFirst(this.getByRestriction(null, restriction, null, Limits.oneRec()));
	}
	@Override
	public String getTableFromMetaData() {
		return getMdao().getTableFromMetaData(getDefaultEntityDefinition());
	}
	@Override
	public String[] getPropertiesFromMetaData() {
		return getMdao().getPropertiesFromMetaData(getDefaultEntityDefinition());
	}
	@Override
	public String getColumnFromMetadata(String propertyName) {
		return getMdao().getColumnFromMetadata(getDefaultEntityDefinition(), propertyName);
	}
	@Override
	public String getPropertyTypeFromMetaData(String property) {
		return getMdao().getPropertyTypeFromMetaData(getDefaultEntityDefinition(), property);
	}
	@SuppressWarnings("unchecked")
	protected Collection<T> getByAttribute(RestrictionAttribute attribute, SOrder[] orderBy, Limits limits) {
		Collection<?> collection = getDao().getByAttribute(getDefaultEntityDefinition(), attribute, orderBy, limits);
		return (Collection<T>)collection;
	}	
	protected T getOneByAttribute(RestrictionAttribute attribute) {
		return getFirst(this.getByAttribute(attribute, null, Limits.oneRec()));
	}
}
